package com.mtiiot.fstcpserver.fastdfs.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class ProtoCommon
{
  public static final byte FDFS_PROTO_CMD_QUIT = 82;
  public static final byte TRACKER_PROTO_CMD_SERVER_LIST_GROUP = 91;
  public static final byte TRACKER_PROTO_CMD_SERVER_LIST_STORAGE = 92;
  public static final byte TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE = 93;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE = 101;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE = 102;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE = 103;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE = 104;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL = 105;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL = 106;
  public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL = 107;
  public static final byte TRACKER_PROTO_CMD_RESP = 100;
  public static final byte FDFS_PROTO_CMD_ACTIVE_TEST = 111;
  public static final byte STORAGE_PROTO_CMD_UPLOAD_FILE = 11;
  public static final byte STORAGE_PROTO_CMD_DELETE_FILE = 12;
  public static final byte STORAGE_PROTO_CMD_SET_METADATA = 13;
  public static final byte STORAGE_PROTO_CMD_DOWNLOAD_FILE = 14;
  public static final byte STORAGE_PROTO_CMD_GET_METADATA = 15;
  public static final byte STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE = 21;
  public static final byte STORAGE_PROTO_CMD_QUERY_FILE_INFO = 22;
  public static final byte STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE = 23;
  public static final byte STORAGE_PROTO_CMD_APPEND_FILE = 24;
  public static final byte STORAGE_PROTO_CMD_MODIFY_FILE = 34;
  public static final byte STORAGE_PROTO_CMD_TRUNCATE_FILE = 36;
  public static final byte STORAGE_PROTO_CMD_RESP = 100;
  public static final byte FDFS_STORAGE_STATUS_INIT = 0;
  public static final byte FDFS_STORAGE_STATUS_WAIT_SYNC = 1;
  public static final byte FDFS_STORAGE_STATUS_SYNCING = 2;
  public static final byte FDFS_STORAGE_STATUS_IP_CHANGED = 3;
  public static final byte FDFS_STORAGE_STATUS_DELETED = 4;
  public static final byte FDFS_STORAGE_STATUS_OFFLINE = 5;
  public static final byte FDFS_STORAGE_STATUS_ONLINE = 6;
  public static final byte FDFS_STORAGE_STATUS_ACTIVE = 7;
  public static final byte FDFS_STORAGE_STATUS_NONE = 99;
  public static final byte STORAGE_SET_METADATA_FLAG_OVERWRITE = 79;
  public static final byte STORAGE_SET_METADATA_FLAG_MERGE = 77;
  public static final int FDFS_PROTO_PKG_LEN_SIZE = 8;
  public static final int FDFS_PROTO_CMD_SIZE = 1;
  public static final int FDFS_GROUP_NAME_MAX_LEN = 16;
  public static final int FDFS_IPADDR_SIZE = 16;
  public static final int FDFS_DOMAIN_NAME_MAX_SIZE = 128;
  public static final int FDFS_VERSION_SIZE = 6;
  public static final int FDFS_STORAGE_ID_MAX_SIZE = 16;
  public static final String FDFS_RECORD_SEPERATOR = "\001";
  public static final String FDFS_FIELD_SEPERATOR = "\002";
  public static final int TRACKER_QUERY_STORAGE_FETCH_BODY_LEN = 39;
  public static final int TRACKER_QUERY_STORAGE_STORE_BODY_LEN = 40;
  protected static final int PROTO_HEADER_CMD_INDEX = 8;
  protected static final int PROTO_HEADER_STATUS_INDEX = 9;
  public static final byte FDFS_FILE_EXT_NAME_MAX_LEN = 6;
  public static final byte FDFS_FILE_PREFIX_MAX_LEN = 16;
  public static final byte FDFS_FILE_PATH_LEN = 10;
  public static final byte FDFS_FILENAME_BASE64_LENGTH = 27;
  public static final byte FDFS_TRUNK_FILE_INFO_LEN = 16;
  public static final byte ERR_NO_ENOENT = 2;
  public static final byte ERR_NO_EIO = 5;
  public static final byte ERR_NO_EBUSY = 16;
  public static final byte ERR_NO_EINVAL = 22;
  public static final byte ERR_NO_ENOSPC = 28;
  public static final byte ECONNREFUSED = 61;
  public static final byte ERR_NO_EALREADY = 114;
  public static final long INFINITE_FILE_SIZE = 288230376151711744L;
  public static final long APPENDER_FILE_SIZE = 288230376151711744L;
  public static final long TRUNK_FILE_MARK_SIZE = 576460752303423488L;
  public static final long NORMAL_LOGIC_FILENAME_LENGTH = 44L;
  public static final long TRUNK_LOGIC_FILENAME_LENGTH = 60L;

  public static String getStorageStatusCaption(byte status)
  {
    switch (status)
    {
    case 0:
      return "INIT";
    case 1:
      return "WAIT_SYNC";
    case 2:
      return "SYNCING";
    case 3:
      return "IP_CHANGED";
    case 4:
      return "DELETED";
    case 5:
      return "OFFLINE";
    case 6:
      return "ONLINE";
    case 7:
      return "ACTIVE";
    case 99:
      return "NONE";
    }
    return "UNKOWN";
  }

  public static byte[] packHeader(byte cmd, long pkg_len, byte errno)
    throws UnsupportedEncodingException
  {
    byte[] header = new byte[10];
    Arrays.fill(header, (byte)0);

    byte[] hex_len = long2buff(pkg_len);
    System.arraycopy(hex_len, 0, header, 0, hex_len.length);
    header[8] = cmd;
    header[9] = errno;
    return header;
  }

  public static RecvHeaderInfo recvHeader(InputStream in, byte expect_cmd, long expect_body_len)
    throws IOException
  {
    byte[] header = new byte[10];
    int bytes;
    if ((bytes = in.read(header)) != header.length)
    {
      throw new IOException(new StringBuilder().append("recv package size ").append(bytes).append(" != ").append(header.length).toString());
    }

    if (header[8] != expect_cmd)
    {
      throw new IOException(new StringBuilder().append("recv cmd: ").append(header[8]).append(" is not correct, expect cmd: ").append(expect_cmd).toString());
    }

    if (header[9] != 0)
    {
      return new RecvHeaderInfo(header[9], 0L);
    }

    long pkg_len = buff2long(header, 0);
    if (pkg_len < 0L)
    {
      throw new IOException(new StringBuilder().append("recv body length: ").append(pkg_len).append(" < 0!").toString());
    }

    if ((expect_body_len >= 0L) && (pkg_len != expect_body_len))
    {
      throw new IOException(new StringBuilder().append("recv body length: ").append(pkg_len).append(" is not correct, expect length: ").append(expect_body_len).toString());
    }

    return new RecvHeaderInfo((byte)0, pkg_len);
  }

  public static RecvPackageInfo recvPackage(InputStream in, byte expect_cmd, long expect_body_len)
    throws IOException
  {
    RecvHeaderInfo header = recvHeader(in, expect_cmd, expect_body_len);
    if (header.errno != 0)
    {
      return new RecvPackageInfo(header.errno, null);
    }

    byte[] body = new byte[(int)header.body_len];
    int totalBytes = 0;
    int remainBytes = (int)header.body_len;

    while (totalBytes < header.body_len)
    {
      int bytes;
      if ((bytes = in.read(body, totalBytes, remainBytes)) < 0)
      {
        break;
      }

      totalBytes += bytes;
      remainBytes -= bytes;
    }

    if (totalBytes != header.body_len)
    {
      throw new IOException(new StringBuilder().append("recv package size ").append(totalBytes).append(" != ").append(header.body_len).toString());
    }

    return new RecvPackageInfo((byte)0, body);
  }

  public static NameValuePair[] split_metadata(String meta_buff)
  {
    return split_metadata(meta_buff, "\001", "\002");
  }

  public static NameValuePair[] split_metadata(String meta_buff, String recordSeperator, String filedSeperator)
  {
    String[] rows = meta_buff.split(recordSeperator);
    NameValuePair[] meta_list = new NameValuePair[rows.length];
    for (int i = 0; i < rows.length; i++)
    {
      String[] cols = rows[i].split(filedSeperator, 2);
      meta_list[i] = new NameValuePair(cols[0]);
      if (cols.length == 2)
      {
        meta_list[i].setValue(cols[1]);
      }
    }

    return meta_list;
  }

  public static String pack_metadata(NameValuePair[] meta_list)
  {
    if (meta_list.length == 0)
    {
      return "";
    }

    StringBuffer sb = new StringBuffer(32 * meta_list.length);
    sb.append(meta_list[0].getName()).append("\002").append(meta_list[0].getValue());
    for (int i = 1; i < meta_list.length; i++)
    {
      sb.append("\001");
      sb.append(meta_list[i].getName()).append("\002").append(meta_list[i].getValue());
    }

    return sb.toString();
  }

  public static void closeSocket(Socket sock)
    throws IOException
  {
    byte[] header = packHeader((byte)82, 0L, (byte)0);
    sock.getOutputStream().write(header);
    sock.close();
  }

  public static boolean activeTest(Socket sock)
    throws IOException
  {
    byte[] header = packHeader((byte)111, 0L, (byte)0);
    sock.getOutputStream().write(header);

    RecvHeaderInfo headerInfo = recvHeader(sock.getInputStream(), (byte)100, 0L);
    return headerInfo.errno == 0;
  }

  public static byte[] long2buff(long n)
  {
    byte[] bs = new byte[8];
    bs[0] = ((byte)(int)(n >> 56 & 0xFF));
    bs[1] = ((byte)(int)(n >> 48 & 0xFF));
    bs[2] = ((byte)(int)(n >> 40 & 0xFF));
    bs[3] = ((byte)(int)(n >> 32 & 0xFF));
    bs[4] = ((byte)(int)(n >> 24 & 0xFF));
    bs[5] = ((byte)(int)(n >> 16 & 0xFF));
    bs[6] = ((byte)(int)(n >> 8 & 0xFF));
    bs[7] = ((byte)(int)(n & 0xFF));

    return bs;
  }

  public static long buff2long(byte[] bs, int offset)
  {
    return (bs[offset] >= 0 ? bs[offset] : 256 + bs[offset]) << 56 | (bs[(offset + 1)] >= 0 ? bs[(offset + 1)] : 256 + bs[(offset + 1)]) << 48 | (bs[(offset + 2)] >= 0 ? bs[(offset + 2)] : 256 + bs[(offset + 2)]) << 40 | (bs[(offset + 3)] >= 0 ? bs[(offset + 3)] : 256 + bs[(offset + 3)]) << 32 | (bs[(offset + 4)] >= 0 ? bs[(offset + 4)] : 256 + bs[(offset + 4)]) << 24 | (bs[(offset + 5)] >= 0 ? bs[(offset + 5)] : 256 + bs[(offset + 5)]) << 16 | (bs[(offset + 6)] >= 0 ? bs[(offset + 6)] : 256 + bs[(offset + 6)]) << 8 | (bs[(offset + 7)] >= 0 ? bs[(offset + 7)] : 256 + bs[(offset + 7)]);
  }

  public static int buff2int(byte[] bs, int offset)
  {
    return (bs[offset] >= 0 ? bs[offset] : 256 + bs[offset]) << 24 | (bs[(offset + 1)] >= 0 ? bs[(offset + 1)] : 256 + bs[(offset + 1)]) << 16 | (bs[(offset + 2)] >= 0 ? bs[(offset + 2)] : 256 + bs[(offset + 2)]) << 8 | (bs[(offset + 3)] >= 0 ? bs[(offset + 3)] : 256 + bs[(offset + 3)]);
  }

  public static String getIpAddress(byte[] bs, int offset)
  {
    if ((bs[0] == 0) || (bs[3] == 0))
    {
      return "";
    }

    StringBuilder sbResult = new StringBuilder(16);
    for (int i = offset; i < offset + 4; i++)
    {
      int n = bs[i] >= 0 ? bs[i] : 256 + bs[i];
      if (sbResult.length() > 0)
      {
        sbResult.append(".");
      }
      sbResult.append(String.valueOf(n));
    }

    return sbResult.toString();
  }

  public static String md5(byte[] source)
    throws NoSuchAlgorithmException
  {
    char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(source);
    byte[] tmp = md.digest();
    char[] str = new char[32];
    int k = 0;
    for (int i = 0; i < 16; i++)
    {
      str[(k++)] = hexDigits[(tmp[i] >>> 4 & 0xF)];
      str[(k++)] = hexDigits[(tmp[i] & 0xF)];
    }

    return new String(str);
  }

  public static String getToken(String remote_filename, int ts, String secret_key)
    throws UnsupportedEncodingException, NoSuchAlgorithmException, MyException
  {
    byte[] bsFilename = remote_filename.getBytes(ClientGlobal.g_charset);
    byte[] bsKey = secret_key.getBytes(ClientGlobal.g_charset);
    byte[] bsTimestamp = new Integer(ts).toString().getBytes(ClientGlobal.g_charset);

    byte[] buff = new byte[bsFilename.length + bsKey.length + bsTimestamp.length];
    System.arraycopy(bsFilename, 0, buff, 0, bsFilename.length);
    System.arraycopy(bsKey, 0, buff, bsFilename.length, bsKey.length);
    System.arraycopy(bsTimestamp, 0, buff, bsFilename.length + bsKey.length, bsTimestamp.length);

    return md5(buff);
  }

  public static String genSlaveFilename(String master_filename, String prefix_name, String ext_name)
    throws MyException
  {
    if (master_filename.length() < 34)
    {
      throw new MyException(new StringBuilder().append("master filename \"").append(master_filename).append("\" is invalid").toString());
    }

    int dotIndex = master_filename.indexOf(46, master_filename.length() - 7);
    String true_ext_name;
    //String true_ext_name;
    if (ext_name != null)
    {
      //String true_ext_name;
      if (ext_name.length() == 0)
      {
        true_ext_name = "";
      }
      else
      {
        //String true_ext_name;
        if (ext_name.charAt(0) == '.')
        {
          true_ext_name = ext_name;
        }
        else
        {
          true_ext_name = new StringBuilder().append(".").append(ext_name).toString();
        }
      }
    }
    else
    {
      //String true_ext_name;
      if (dotIndex < 0)
      {
        true_ext_name = "";
      }
      else
      {
        true_ext_name = master_filename.substring(dotIndex);
      }
    }

    if ((true_ext_name.length() == 0) && (prefix_name.equals("-m")))
    {
      throw new MyException(new StringBuilder().append("prefix_name \"").append(prefix_name).append("\" is invalid").toString());
    }

    if (dotIndex < 0)
    {
      return new StringBuilder().append(master_filename).append(prefix_name).append(true_ext_name).toString();
    }

    return new StringBuilder().append(master_filename.substring(0, dotIndex)).append(prefix_name).append(true_ext_name).toString();
  }

  public static class RecvHeaderInfo
  {
    public byte errno;
    public long body_len;

    public RecvHeaderInfo(byte errno, long body_len)
    {
      this.errno = errno;
      this.body_len = body_len;
    }
  }

  public static class RecvPackageInfo
  {
    public byte errno;
    public byte[] body;

    public RecvPackageInfo(byte errno, byte[] body)
    {
      this.errno = errno;
      this.body = body;
    }
  }
}